Skip to content

feat(core): framework-agnostic engine layer (PR 1/3)#137

Draft
itay-dar-lmnd wants to merge 17 commits into
PythonNest:mainfrom
itay-dar-lmnd:pynest-protocol
Draft

feat(core): framework-agnostic engine layer (PR 1/3)#137
itay-dar-lmnd wants to merge 17 commits into
PythonNest:mainfrom
itay-dar-lmnd:pynest-protocol

Conversation

@itay-dar-lmnd

Copy link
Copy Markdown
Contributor

Summary

Introduces a framework-agnostic engine layer so PyNest is no longer hard-wired to FastAPI. Routing, parameter binding, lifecycle, exception handling, middleware, CORS and WebSockets are now expressed against a neutral contract (AbstractHttpAdapter), with FastAPI reimplemented as the first adapter behind it. No user-facing API changes — existing apps keep working.

This is PR 1 of 3 in a stack:

  1. Framework-agnostic core (this PR)
  2. Litestar adapter
  3. Blacksheep adapter

What's inside

Neutral engine contract (nest/engine/)

  • http_adapter.pyAbstractHttpAdapter, the NestJS-inspired engine contract every framework implements
  • route_spec.py, params.py, execution_context.py, types.py — framework-neutral descriptors (RouteSpec, ParamSpec, ExecutionContext, HttpMethod)
  • nest/http/ — a stable facade (Request/Response/Depends/HTTPException) so app code imports from PyNest, not the underlying framework

FastAPI as the first adapter (nest/engines/fastapi/)

  • adapter.py + params.py — the existing FastAPI behavior, now living behind AbstractHttpAdapter
  • Core (pynest_application.py, pynest_factory.py, route_resolver.py, decorators) rewired to drive the adapter instead of calling FastAPI directly

Conformance test suite (tests/test_engine/)

  • Adapter-parametrized conformance tests (routing, param binding, request accessors, lifespan, middleware, CORS, exception handlers, WebSockets) — the contract every future engine must satisfy
  • Unit tests for the neutral descriptors and the facade

Docs (docs/engine/)

  • Architecture overview, FastAPI adapter notes, a "writing an adapter" guide, and a 0.7 migration note

Compatibility

No breaking changes for app authors. FastAPI remains the default engine; the abstraction sits underneath.

itay-dar-lmnd and others added 17 commits June 11, 2026 17:10
…point alias

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…riptor

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ctHttpAdapter

Rename _PyNestABCMeta to _ABCMetaMROFix and reduce its scope to only the
minimal Python 3.9 ABCMeta MRO fix; keep MRO-walk in __init__ which is
required for mixin-style subclasses where the concrete provider appears
after AbstractHttpAdapter in the MRO.
… clean test design

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…tion aliases

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…rized suite

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ntracts, guards, filters, lifecycle, DI

Bugs discovered during smoke testing (to fix in PR 3):
1. from __future__ import annotations in controllers breaks @Body/@query param
   binding — Pydantic v2 TypeAdapter receives ForwardRef instead of resolved type.
   Workaround: drop future annotations from controller files.
2. Route collector registers paths correctly but test URLs must omit trailing
   slash (FastAPI redirect_slashes=True default redirects /users/ -> /users).
…ter wrappers

Three related fixes in nest/common/decorators.py and route_resolver.py:

1. wrap_param_decorators: call typing.get_type_hints(endpoint) to resolve string
   annotations from 'from __future__ import annotations' before building FastAPI
   Depends() signatures. Propagate resolved return annotation and __annotations__
   onto the wrapper so FastAPI's response model serialization also works.

2. _wrap_with_filters: propagate resolved __annotations__ from the original
   endpoint onto filter_wrapper for the same reason.

3. _apply_pipes: catch ValueError/TypeError from pipe.transform() and re-raise
   as HTTP 422, preventing raw exceptions from propagating through the ASGI stack.

test-app: expand from 23 to 61 tests across 6 modules, 3 compat tests, 5 stress tests

New modules:
- catalog: nested Pydantic models, variant stock management, cross-module export
- pipeline: custom param decorators (createParamDecorator), pipes (TrimPipe,
  ClampPipe, PositiveIntPipe), async endpoints, Req()/Ip() injection
- auth: cross-module DI (AuthService injects CatalogService), multi-guard
  (BearerGuard + AdminGuard), token management via shared TokenStore provider

Stress tests: 50 concurrent reads, 30 concurrent async computes, 100 rapid
fire writes+reads, 200-variant large payload, 200-req throughput baseline.
- Remove test-app/ from the uv workspace members so it's no longer part of
  the published package or git history going forward.
- Untrack all test-app/ files via 'git rm -r --cached' (local copies preserved
  for continued use during PR-1 development).
- Add test-app/ to .gitignore.

The PR-1 engine contracts and the fix to nest/common/decorators.py +
route_resolver.py remain unchanged. Full test suite still passes (188 tests).
PR 2 — nest/engines/fastapi/ FastAPIAdapter:
- Full AbstractHttpAdapter implementation (18 abstract methods)
- ParamSpec → FastAPI Depends/Body/Query/Path/Header translation
- ExceptionFilter wrapping, startup/shutdown hook routing, CORS, WS routes
- Constructor accepts FastAPI kwargs OR an existing FastAPI instance

PR 3 — cutover from direct FastAPI calls to adapter routing:
- PyNestFactory.create accepts adapter= parameter (default FastAPIAdapter)
- PyNestApp holds adapter; backward-compat shims for .http_server, .use, etc.
- RoutesResolver emits RouteSpec and calls adapter.add_route(spec)
- nest/common/decorators.py is now engine-neutral (returns ParamSpec); the
  FastAPI binding logic lives in nest/engines/fastapi/params.py
- HttpMethod canonical location moved to nest.engine.types; the old
  nest.core.decorators.http_method.HTTPMethod re-exports it
- Constructors accept either adapter or raw FastAPI for 0.6 compatibility

PR 4 — conformance suite + 4 doc pages:
- 17 conformance tests covering route registration, param binding, request
  accessors, middleware, exception handlers, lifespan, CORS, WebSockets
- docs/engine/{overview,fastapi_adapter,writing_an_adapter,migration_0.7}.md
- mkdocs.yml updated with new Engine section

Verification:
- 218 root tests pass (up from 188 baseline; new engine tests included)
- 77 test-app tests pass — including 9 SQLite DB tests, 4 WebSocket gateway
  tests, and 3 cutover assertions that verify FastAPIAdapter is in the loop
- All 5 stress tests still pass (50 concurrent reads, 30 concurrent async
  computes, 100 rapid-fire writes+reads, 200-variant payload, 200-req
  throughput baseline)
- Zero behavioral edits to existing test files

Phase-1 known coupling (documented in docs/engine/fastapi_adapter.md):
- Guards with security_scheme remain FastAPI-only until phase 2
- WebSocket gateways remain FastAPI-only until phase 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant